/*
 * Daniel R Padilla
 *
 * Copyright (c) 2008, Daniel R Padilla
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this distribution; if not, write to:
 * Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor
 * Boston, MA  02110-1301  USA
 */
package org.databasefacade.select;

import java.util.ArrayList;

import org.databasefacade.table.ConstraintKey;
import org.databasefacade.table.ConstraintKeyList;
import org.databasefacade.table.Table;
import org.databasefacade.table.column.Column;
import org.databasefacade.translator.Translator;

public class From {
	protected Table fromTable;
	protected String fromAlias;
	protected ArrayList joins= new ArrayList();
	protected Translator translator;
	
	/*
	 * Create a from clause. DO NOT PROVIDE
	 * any other way to set the from table.
	 * 
	 */
	public From(Table table) {
		fromTable = table;
		fromAlias = table.getTableAlias();
		this.translator = table.getDBHelper().getTranslator();
	}

	public Translator getTranslator() {
		return translator;
	}
	
	/*
	 * Methods used to get the information for native output
	 */
	public Table getFromTable() {
		return fromTable;
	}
	public String getFromTableName() {
		return fromTable.getTableName();
	}
	public String getFromTableAlias() {
		if ( fromAlias.equals(getFromTableName()) ) {
			return "";
		} else {
			return fromAlias;
		}
	}
	public int getNumberOfJoins() {
		return joins.size();
	}
	public Join getJoin(int i) {
		return (Join) joins.get(i);
	}
	
	
	/*
	 * Inner joins
	 */
	public On join(Table table) {
		return join(Join.INNER, table);
	}
	
	public On join(Table table, QueryHints hints) {
		return join(Join.INNER, table, hints);
	}
	
	/*
	 * Left Outer joins
	 */
	public On leftJoin(Table table) {
		return join(Join.LEFT_OUTER, table);
	}
	
	public On leftJoin(Table table, QueryHints hints) {
		return join(Join.LEFT_OUTER, table, hints);
	}

	
	/*
	 * Right Outer joins
	 */
	public On rightJoin(Table table) {
		return join(Join.RIGHT_OUTER, table);
	}
	
	public On rightJoin(Table table, QueryHints hints) {
		return join(Join.RIGHT_OUTER, table, hints);
	}


	/*
	 * Full Outer joins
	 */
	public On fullJoin(Table table) {
		return join(Join.FULL_OUTER, table);
	}
	
	public On fullJoin(Table table, QueryHints hints) {
		return join(Join.FULL_OUTER, table, hints);
	}

	
	/*
	 * Cross Join
	 */
	public void crossJoin(Table table) {
		Join join = new Join(Join.CROSS, table, null, null);
		joins.add(join);
	}
	
	
	/**
	 * The method build the SQL statement that will be returned
	 * from the formation of the from clause. The statement build
	 * supports both comma delimited and ansi SQL JOIN notation.
	 * 
	 * @return
	 */
	public String getSQL() {
		StringBuilder sb = new StringBuilder("");
		
		// Update statement needs this suppressed
		if (fromTable == null) {
			return "";
		}
		
		// Select statement will always have a from table
		if ( fromTable.getTableName().equalsIgnoreCase(fromAlias) ) {
			sb.append("FROM " + fromTable.getTableName());
		} else {
			sb.append("FROM " + fromTable.getTableName() + " " + fromAlias);
		}
		
		for (int i=0; i < joins.size(); i++) {
			Join join = (Join) joins.get(i);
			sb.append(join.getSQL());
		}

		return sb.toString();
	}
	
	public String toString() {
		return getSQL();
	}
	
	/**
	 * Single table joined to the from table.
	 * 
	 * @param table
	 * @return
	 */
	public On join(short joinType, Table table) {
		On on = new On();
		Join join = new Join(joinType, table, on, null);
		compareKeys(table,on);
		joins.add(join);
		return join.on();
	}
	
	/**
	 * Single table with query hints
	 * 
	 * @param table
	 * @param hints
	 * @return
	 */
	public On join(short joinType, Table table, QueryHints hints) {
		On on = new On();
		Join join = new Join(joinType, table, on, null);
		compareKeys(table,on);
		joins.add(join);
		return join.on();
	}
	
	public From prepareForAnsiUpdate(Where where) {
		// Make a copy
		From prep = new From(fromTable);
		for (int i=0; i < joins.size(); i++) {
			prep.joins.add(getJoin(i));
		}
		
		// Make the adjustments for update
		if ( prep.joins.size() > 0 ) {
			Join join = prep.getJoin(0);
			fromTable = join.getTable();
			fromAlias = join.getTableAlias();
			On on = join.on();
			where.conditions().and(on.getOnCompare());
			if ( on.getConditions().size() > 0 ) {
				where.conditions().and(on.getConditions());
			}
			prep.joins.remove(0);
		} else {
			prep.fromTable=null;
			prep.fromAlias=null;
		}
		return prep;
	}
	
	/**
	 * Walk the hierarchy
	 */
	private void compareKeys(Table leaf, On on) {
		compareKeys(leaf, fromTable, on);
		compareKeys(fromTable, leaf, on);
		for (int i=0; i < joins.size(); i++) {
			Join join = (Join) joins.get(i);
			compareKeys(leaf, join.getTable(), on);
			compareKeys(join.getTable(), leaf, on);
		}
	}
	
	/**
	 * Find the foreign key assignment and 
	 */
	private void compareKeys(Table detail, Table master, On on) {
		
		// Find foreign key
		ConstraintKey foreignKey=null;
		ConstraintKeyList keys = detail.getForeignKeys();
		for ( int i=0; i < keys.size(); i++) {
			ConstraintKey key = keys.getKey(i);
			if ( key.getForeignTableName().equals(master.getTableName()) ) {
				foreignKey = key;
				break;
			}			
		}
		
		// If no foreign key is found keep going
		if ( foreignKey == null ) {
			return;
		}
		
		// Setup the join
		boolean isFirst = true;
		for (int i=0; i < foreignKey.getKeyColumns().size(); i++ ) {
			Column col = foreignKey.getKeyColumns().getColumn(i);
			String fColName = foreignKey.getForeignKeyColumnName(i);
			Column fcol = master.getColumn(fColName);
			if ( isFirst ) {
				on.on(col, fcol);
				isFirst=false;
			} else {
				on.on().and().equalToField(col, fcol);
			}
		}
	}
	

}
